//#########################################################################
//                              WackiEngie
//           Copyright (c) 2014 Marc Wacker. All rights reserved.
//#########################################################################

#include "WeFramebuffer.h"
#include "WeTexture.h"
#include "WeTexture2D.h"
#include "WeTextureCube.h"

namespace WackiEngine
{

	struct Framebuffer::TextureAttachment {
		const Texture *texture;
		GLenum attachment;
		GLenum target; // either texture->target() or a cubemap face target
		GLint level;
		GLint layer; // 3D and array textures only

		TextureAttachment(
			) : texture(0),
			attachment(GL_NONE),
			target(GL_NONE),
			level(0),
			layer(0) {
		}

		TextureAttachment(
			const Texture *_texture,
			GLenum _attachment,
			GLenum _target,
			GLint _level = 0,
			GLint _layer = 0
			) : texture(_texture),
			attachment(_attachment),
			target(_target),
			level(_level),
			layer(_layer) {
		}
	};


	//	PUBLIC:

	//	CTORS/DTORS:

	/*----------------------------------------------------------------------------*/

	Framebuffer::Framebuffer(GLenum target)
		: _handle(GL_ZERO),
		_status(GL_FRAMEBUFFER_UNDEFINED),
		_maxAttachments(-1),
		_maxDrawBuffers(-1)
	{
		_size[0] = _size[1] = 0;

		glGenFramebuffers(1, &_handle);

		bind(target);
		init();
	}

	Framebuffer::~Framebuffer()
	{
		if(_handle)
			glDeleteFramebuffers(1, &_handle);
	}

	const Texture* Framebuffer::attachment(GLenum attachment)
	{
		for(auto &ta : _attachments) {
			if(ta.attachment == attachment)
				return ta.texture;
		}

		return NULL;
	}

	void Framebuffer::attach(const Texture2D *texture, GLenum attachment, GLint level)
	{
		assert(_handle != GL_ZERO);

		glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, texture->target(), texture->handle(), level);

		addTextureAttachment(TextureAttachment(texture, attachment, texture->target(), level, 0));
	}

	void Framebuffer::attach(const TextureCube *texture, GLenum face, GLenum attachment, GLint level)
	{
		assert(_handle != GL_ZERO);
		assert((face == GL_TEXTURE_CUBE_MAP_POSITIVE_X) ||
			(face == GL_TEXTURE_CUBE_MAP_NEGATIVE_X) ||
			(face == GL_TEXTURE_CUBE_MAP_POSITIVE_Y) ||
			(face == GL_TEXTURE_CUBE_MAP_NEGATIVE_Y) ||
			(face == GL_TEXTURE_CUBE_MAP_POSITIVE_Z) ||
			(face == GL_TEXTURE_CUBE_MAP_NEGATIVE_Z));

		glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, face, texture->handle(), level);
		addTextureAttachment(TextureAttachment(texture, attachment, face, level, 0));

	}

	void Framebuffer::init()
	{

		// get implementation-dependant limits:
		glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &_maxAttachments);
		_maxAttachments += 2; // add 2 for depth/stencil attachments.

		glGetIntegerv(GL_MAX_DRAW_BUFFERS, &_maxDrawBuffers);
	}

	inline void Framebuffer::addTextureAttachment(const TextureAttachment &texa)
	{
		// check if the attachment already exists
		bool attachmentSet = false;
		for(TextureAttachment &ta : _attachments)
		{
			if(ta.attachment == texa.attachment) {
				attachmentSet = true;
				ta = texa;
			}
		}

		// the attachment is new, push it onto the list
		if(!attachmentSet) {
			// check if we're still in our limits
			assert((GLsizei)_attachments.size() < _maxAttachments);
			_attachments.push_back(texa);

			// update the draw buffers since we have new attachment
			updateDrawBuffers();
		}

		// check if our size changed
		updateSize();
		// sanity check the fbo
		updateStatus();
	}

	inline void Framebuffer::updateDrawBuffers()
	{
		// clear the current list
		_drawBuffers.clear();
		for(auto &ta : _attachments) {
			switch(ta.attachment) {
			case GL_DEPTH_ATTACHMENT:
			case GL_STENCIL_ATTACHMENT:
			case GL_DEPTH_STENCIL_ATTACHMENT:
				break;
			default:
				_drawBuffers.push_back(ta.attachment);
				break;
			}
		}

		glDrawBuffers((GLsizei)_drawBuffers.size(), &_drawBuffers[0]);
	}

	inline void Framebuffer::updateSize()
	{
		_size[0] = _size[1] = 0;

		for(auto& ta : _attachments) {
			GLsizei texWidth = ta.texture->width();
			GLsizei texHeight = ta.texture->height();
			if(_size[0] == 0 || _size[0] > texWidth)
				_size[0] = texWidth;
			if(_size[1] == 0 || _size[1] > texHeight)
				_size[1] = texHeight;
		}

		_texelSize[0] = 1.0f / _size[0];
		_texelSize[1] = 1.0f / _size[1];
	}

	inline void Framebuffer::updateStatus()
	{
		_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
	}

}